#ifndef __SKIN_H__
#define __SKIN_H__

#include <GL/GL.h>
#include <wx/bitmap.h>
#include <wx/treectrl.h>
#include <wx/string.h>
#include <wx/graphics.h>
#include <wx/dcmemory.h>
#include <wx/rawbmp.h>
#include <IL/il.h>

#include <boost/filesystem.hpp>

class UndoBuffer
{
public:
	class Skin *Skin;

	UndoBuffer(class Skin *skin) :
		Skin(skin)
	{
	};

	bool CanUndo()
	{
		return false;
	}
};

inline std::string GetItemPath(const std::string &pathSeparator, const wxTreeCtrl *Tree, const wxTreeItemId &Index)
{
	std::string ret = Tree->GetItemText(Index);

	for (wxTreeItemId parent = Tree->GetItemParent(Index); ; parent = Tree->GetItemParent(parent))
	{
		if (Tree->GetRootItem() == parent)
			break;
		if (!parent)
			break;
		ret.insert(0, Tree->GetItemText(parent) + pathSeparator);
	}

	return ret;
}

inline void BindTexture(GLuint tex)
{
	static GLuint _currentTex = -1;

	if (_currentTex == tex)
		return;

	_currentTex = tex;
	glBindTexture(GL_TEXTURE_2D, tex);
}

inline GLuint LoadGLImage(wxString path, wxSize &outSize)
{
	GLuint tex;

	glGenTextures(1, &tex);

	ILuint ilImage = ilGenImage();
	ilBindImage(ilImage);
	ILboolean ret = ilLoadImage(path.c_str());
	
	int err = ilGetError();

	if (err != IL_NO_ERROR || !ret)
	{
		glDeleteTextures(1, &tex);
		ilDeleteImage(ilImage);
		throw std::exception();
	}

	if (ilGetInteger(IL_IMAGE_BPP) != 4)
	{
		glDeleteTextures(1, &tex);
		ilDeleteImage(ilImage);
		throw std::exception();
	}

	outSize = wxSize(ilGetInteger(IL_IMAGE_WIDTH), ilGetInteger(IL_IMAGE_HEIGHT));

	BindTexture(tex);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, ilGetInteger(IL_IMAGE_WIDTH), ilGetInteger(IL_IMAGE_HEIGHT), 0, GL_RGBA, GL_UNSIGNED_BYTE, ilGetData());
	
	ilDeleteImage(ilImage);

	err = glGetError();

	if (err != GL_NO_ERROR)
	{
		glDeleteTextures(1, &tex);
		throw std::exception();
	}

	return tex;
}

inline GLuint LoadGLImage(wxString path)
{
	wxSize size;
	return LoadGLImage(path, size);
}

class Skin : public wxTreeItemData
{
public:
	wxBitmap *Image;
	wxBitmap *Head;
	GLuint GLImage;
	UndoBuffer Undo;
	bool Dirty;
	wxSize Size;
	wxTreeItemId Index;
	wxTreeCtrl *Owner;

	int GetWidth() { return Size.x; }
	int GetHeight() { return Size.y; }

	wxString GetName() { return Owner->GetItemText(Index); }
	void SetName(wxString str) { Owner->SetItemText(Index, str); }

	boost::filesystem3::path GetDirectory()
	{
		if (!Owner->GetItemParent(Index))
			return boost::filesystem3::path("Skins");

		return boost::filesystem3::path("Skins\\" + GetItemPath("\\", Owner, Index)).parent_path();
	}

	boost::filesystem3::path GetFile()
	{
		return boost::filesystem3::path(GetDirectory().string() + "\\" + GetName() + ".png");
	}

	Skin(wxString fileName, wxTreeCtrl *owner, wxTreeItemId item) :
		Undo(this),
		Owner(owner),
		Index(item),
		Image(NULL),
		Head(NULL),
		GLImage(0),
		Dirty(false)
	{
		SetName(boost::filesystem3::path(fileName).stem().string());
		owner->SetItemData(item, this);
	}

	~Skin()
	{
		if (GLImage != 0)
		{
			glDeleteTextures(1, &GLImage);
			GLImage = 0;
		}

		if (Head != NULL)
		{
			delete Head;
			Head = NULL;
		}

		if (Image != NULL)
		{
			delete Image;
			Image = NULL;
		}
	}

	void SetImages()
	{
		if (Head != NULL)
		{
			delete Head;
			Head = NULL;
			glDeleteTextures(1, &GLImage);
		}

		/*Image = new wxBitmap(GetFile().string(), wxBITMAP_TYPE_PNG);
		Size = Image->GetSize();

		float scale = Size.GetWidth() / 64.0f;
		int headSize = (int)(8.0f * scale);
		int helmetLoc = (int)(40.0f * scale);

		Head = new wxBitmap(headSize, headSize);

		{
			wxMemoryDC memDC;
			memDC.SelectObject(*Head);

			wxMemoryDC imageDC;
			imageDC.SelectObject(*Image);

			memDC.Blit(0, 0, headSize, headSize, &imageDC, headSize, headSize);
			memDC.Blit(0, 0, headSize, headSize, &imageDC, helmetLoc, headSize);
			//g.DrawImage(Image, new Rectangle(0, 0, headSize, headSize), new Rectangle(headSize, headSize, headSize, headSize), GraphicsUnit.Pixel);
			//g.DrawImage(Image, new Rectangle(0, 0, headSize, headSize), new Rectangle(helmetLoc, headSize, headSize, headSize), GraphicsUnit.Pixel);
		}*/

		// P: This was done in the .NET version, but, I think I'll leave it allocated for now...
		//delete Image;
		//Image = NULL;

		GLImage = LoadGLImage(GetFile().string(), Size);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	}

	wxString ToString()
	{
		if (Dirty)
			return GetName() + " *";
		return GetName();
	}

	void CommitChanges(const GLuint &currentSkin, const bool &save)
	{
		int *data = new int[GetWidth() * GetHeight()];
		BindTexture(currentSkin);
		glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

		BindTexture(GLImage);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, GetWidth(), GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, data);

		if (save)
		{
			wxBitmap tempBitmap (GetWidth(), GetHeight());

			// yay scopes
			{
				wxNativePixelData pd(tempBitmap);
				wxNativePixelData::Iterator fp(pd);

					for (int y = 0; y < GetHeight(); ++y)
						for (int x = 0; x < GetWidth(); ++x)
						{
							int c = data[x + (y * GetWidth())];
							fp.Red() = (c >> 0) & 0xFF;
							fp.Green() = (c >> 8) & 0xFF;
							fp.Blue() = (c >> 16) & 0xFF;
							fp.Alpha() = (c >> 24) & 0xFF;
						}
			}

			tempBitmap.SaveFile(GetFile().string(), wxBITMAP_TYPE_PNG);
			SetImages();

			Dirty = false;
		}
	}

	bool ChangeName(wxString newName)
	{
		if (!newName.EndsWith(".png"))
			newName.Append(".png");

		boost::filesystem3::path newPath = boost::filesystem3::path(GetDirectory().string() + "\\" + newName);

		if (boost::filesystem3::exists(newPath))
			return false;

		boost::filesystem3::rename(GetFile(), newPath);
		SetName(newPath.stem().string());

		return true;
	}

	// TODO: implement this.
	// We may have to do custom nearest-neighbor scaling, because wx doesn't
	// provide a lot of good "scale" routines.
	void Resize(int width, int height)
	{
		/*wxBitmap newBitmap (width, height);
		{
			wxMemoryDC g;
			g.SelectObject(newBitmap);

			{
				//g.SmoothingMode = SmoothingMode.None;
				//g.InterpolationMode = InterpolationMode.NearestNeighbor;
				//g.PixelOffsetMode = PixelOffsetMode.HighQuality;

				wxBitmap temp (GetFile().string(), wxBITMAP_TYPE_PNG);
				g.DrawBitmap(temp, 0, 0, newBitmap.Width, newBitmap.Height);
			}

			newBitmap.Save(File.FullName);
		}

		SetImages();

		Undo.Clear();
		Program.MainForm.CheckUndo();*/
		throw std::exception();
	}
};

#endif